//
//  Timer.h
//  面试题
//
//  Created by Toj on 8/8/21.
//

#pragma mark - 源码参考
https://opensource.apple.com/source/xnu/xnu-3789.51.2/osfmk/kern/mk_timer.c
https://opensource.apple.com/source/xnu/xnu-3789.51.2/osfmk/kern/mk_timer.c.auto.html

#pragma mark - 文章解读
https://www.jianshu.com/p/179603ffb194?hmsr=toutiao.io&utm_campaign=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io

#pragma mark - timer问题
1. timer种类
2. NSTimer 准确吗? 如果不准确为什么?, 怎么去解决?
3. NSTimer 为什么会造成循环引用
4. 循环引用怎么解决
5. 循环引用怎么解决
6. invalidate做了什么事?


#pragma mark - timer种类
1.1> NSTimer
1.2> GCD
1.3> CADisplayLink
1.4> 基于mach底层高精度

#pragma mark - NSTimer 准确吗? 如果不准确为什么?, 怎么去解决?
不准.
因为NSTimer 都是基于runloop的
默认是添加到runloop的defaultmode下 当更新UI,
Runloopl会切换mode更新这样time就会停止, 更新完UI再切换回来,
耽误时间所以就会出现时间不准情况.
这样回答其实不准确, 如果我改变模式用common模式呢?
common = tracking + defaule
2个模式我都走为什么还是不准呢?
因为timer底层有一个偏移时间纠正, 一个最延迟的校验,
有一个最理想的时间,
最晚执行的时间,
如果2个值相等, 说明没延迟, 如果最晚时间 > 最理想时间, 就会出现延迟现象
所以时间会错开一个偏移时间

#pragma mark - NSTimer 为什么会造成循环引用
因为timer运行分2步
1. 创建timer
2. 添加到runloop
首先在创建Timer的时候把target: self传入进去, 这样timer就引用了self.
[NSTimer timerWithTimeInterval:1 target:center selector:@selector(didTapTimerHandle) userInfo:nil repeats: YES];

在timer添加到runloop时候, runloop会强引用timer, 因为runloop一直存在所以timer就一直不被释放
[[NSRunLoop currentRunLoop] addTimer:timer forMode: NSDefaultRunLoopMode];
[[NSRunLoop currentRunLoop] run];

关系: runloop -> timer <...--> self
为什么self 引用timer是....? 因为self 不一定强引用timer
**** 补充一点, 如果self被释放了, 不调用invalidate, timer 还是被runloop引用着, 触发timer事件

#pragma mark 解决办法?
1. NSTimer属性weak修饰
当然这样创建可能被释放, 所以不能直接给weak属性赋值, 可以间接赋值, 先创建后在给weak属性赋值, 这样timer执行, weak也能指向,

2. 在返回前调用 [timer invalidate];
这么写可能不太舒服, 也有可能你做的事还没做完, 建议使用【3.4】

3. 利用中间件【如ObjectCenter.h】
{
    ObjectCenter *center = [[ObjectCenter alloc] strongToWeakObject:self];
    
    self.timer = [NSTimer timerWithTimeInterval:1
                                         target:center
                                       selector:@selector(didTapTimerHandle)
                                       userInfo:nil
                                       repeats:YES];
}

4. 用block新方法 -> scheduledTimerWithTimeInterval
API_AVAILABLE(macosx(10.12), ios(10.0), watchos(3.0), tvos(10.0))

weak weakSelf;
[NSTimer scheduledTimerWithTimeInterval:1 repeats:YES block:^(NSTimer * _Nonnull timer) {
    [weakSelf didTapTimerHandle];
}];
这样都不要在析构函数内调用invalidate

**** 切记最后要在析构方法内调用 invalidate, 因为Runloop内强应用这timer, 即使控制器被释放了, runloop还是在执行着timer操作
- (void)dealloc {
    NSLog(@"%s", __func__);
    [_timer invalidate];
    _timer = nil;
}

#pragma mark - 6. invalidate做了什么事?
1. 断开target强引用
2. 断开runloop强引用
